package com.leetcode.concurrency;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntConsumer;

class ZeroEvenOdd {
    private int n;
    private final Lock lock;
    private final Condition zeroCondition;
    private final Condition oddCondition;
    private final Condition evenCondition;

    private volatile int signal = 1;

    private volatile int output = 1;

    public ZeroEvenOdd(int n) {
        lock = new ReentrantLock();
        zeroCondition = lock.newCondition();
        oddCondition = lock.newCondition();
        evenCondition = lock.newCondition();
        this.n = n;
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        if(n == 1){
            printNumber.accept(0);
            signal = 3;
            oddCondition.signal();
        }
        while (output < n) {
            if (signal != 1 && signal != 2) zeroCondition.await();
            int temp = signal;
            printNumber.accept(0);
            if (signal == 1){
                signal = 3;
                oddCondition.signal();
            }
            else if (temp == 2){
                signal = 4;
                evenCondition.signal();
            }
        }
        lock.unlock();
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        while (output < n) {
            if(signal != 4) evenCondition.await();
            printNumber.accept(output);
            output++;
            signal = 1;
            zeroCondition.signal();
        }
        lock.unlock();
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        if(n == 1){
            if(signal != 3) oddCondition.await();
            printNumber.accept(1);
        }
        try {
            while (output < n) {
                if(signal != 3) oddCondition.await();
                printNumber.accept(output);
                output++;
                signal = 2;
                zeroCondition.signal();
            }
        } finally {
            lock.unlock();
        }
    }
}
